/*
 Copyright (C) 2007 - Michael Opitz
 This program is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License
 as published by the Free Software Foundation; either version 2
 of the License, or (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program; if not, write to the Free Software
 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
*/
#include "gmail.h"

#define GMAIL_ACCOUNT_PATH 			\
	"/.gnome2/epiphany/extensions/data/gmailmanager/accounts.txt"

#define GMAIL_ACCOUNT_GET_PRIVATE(object)	(G_TYPE_INSTANCE_GET_PRIVATE(\
						(object), \
						 GMAIL_ACCOUNT_TYPE, \
						 GmailAccountPriv))

struct _GmailAccountPriv {
	/** 
	 * Stores the mail of the account
	 */
	char *mail;
	/** 
	 * Stores the password of the account
	 */
	char *password;
	/** 
	 * Stores a list of MessageInfo's.
	 */
	GList *messages;
	/** 
	 * Indicates whether the Account is disposed or not.
	 */
	int is_disposed;
};

static GType type = 0;

/** 
 * gmail_account_copy_messages:
 * 
 * @info: a pointer to a MessageInfo struct.
 * @user_data: a GList**
 * 
 * This function is used in a foreach-loop to (deep-)copy a MessageInfo GList 
 * into a new GList.
 * 
 */
void
gmail_account_copy_messages (MessageInfo *info, gpointer user_data);
/** 
 * gmail_account_save_item:
 * 
 * @account: a pointer to a GmailAccount
 * @fp: a file-handler
 *
 * This method saves a GmailAccount to a file.
 *
 */
void
gmail_account_save_item (GmailAccount *account, FILE *fp);

GType
gmail_account_register_type (GTypeModule *module)
{
	const GTypeInfo info = {
		sizeof (GmailAccountClass),
		NULL, 		/* base_init */
		NULL,		/* base_finalize */
		(GClassInitFunc) gmail_account_class_init, 
		NULL, 		/* class_finalize */
		NULL, 		/* class_data */
		sizeof (GmailAccount), 
		0, 		/* n_preallocs */
		(GInstanceInitFunc) gmail_account_instance_init 
	};
	type = g_type_module_register_type (module, 
					    G_TYPE_OBJECT, 
					    "GmailAccount",
					    &info, 0);
	return type;
}

GType 
gmail_account_get_type ()
{
	return type;
}

GObject*
gmail_account_constructor (GType type, 
			   guint n_construct_properties,
			   GObjectConstructParam *construct_properties)
{
	/* Do I really need this?! */
	GObject *obj;
	{
		/* invoke parent constructor */
		GmailAccountClass *klass;
		GObjectClass *parent_class;
		klass = GMAIL_ACCOUNT_CLASS (
			g_type_class_peek (GMAIL_ACCOUNT_TYPE));
		parent_class = G_OBJECT_CLASS (g_type_class_peek_parent(klass));
		obj = parent_class->constructor (type, 
						 n_construct_properties, 
						 construct_properties);
	}
	/* do init stuff */
	
	return obj;
}

void
gmail_account_copy_messages (MessageInfo *info, gpointer user_data)
{
	GList **lst = (GList**) user_data;
	g_assert (info != NULL && lst != NULL);

	MessageInfo *copy = (MessageInfo*) malloc (sizeof (MessageInfo));
	copy->from_mail = g_strdup (info->from_mail);
	copy->from_name = g_strdup (info->from_name);
	copy->subject = g_strdup (info->subject);
	copy->snipped = g_strdup (info->snipped);
	copy->url = g_strdup (info->url);
	(*lst) = g_list_append (*lst, copy);
}

/*
 * setting mail, password and MessageInfo-list
 */
void 
gmail_account_set_property (GObject 	 *object,
			    guint 	 property_id,
			    const GValue *value,
			    GParamSpec 	 *spec)
{
	GmailAccount *self = GMAIL_ACCOUNT (object);

	switch (property_id) {
	case GMAIL_ACCOUNT_MAIL:
		free (self->priv->mail);
		self->priv->mail = g_value_dup_string (value);
		break;
	case GMAIL_ACCOUNT_PASSWORD:
		free (self->priv->password);
		self->priv->password = g_value_dup_string (value);
		break;
	case GMAIL_ACCOUNT_MESSAGES:
		g_list_free (self->priv->messages);
		/* make a deep copy of the message_info struct... */
		self->priv->messages = NULL;
		GList *other = (GList*) g_value_get_pointer (value);

		g_list_foreach (other, (GFunc) gmail_account_copy_messages, 
			        &self->priv->messages);
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, spec);
		break;
	}
}

/*
 * getting email, password and the MessageInfo-list
 */
void
gmail_account_get_property (GObject 	*object,
			    guint	 property_id,
			    GValue	*value,
			    GParamSpec	*pspec)
{
	GmailAccount *self = GMAIL_ACCOUNT (object);

	switch(property_id) {
	case GMAIL_ACCOUNT_MAIL:
		g_value_set_string (value, self->priv->mail);
		break;
	case GMAIL_ACCOUNT_PASSWORD:
		g_value_set_string (value, self->priv->password);
		break;
	case GMAIL_ACCOUNT_MESSAGES:
		g_value_set_pointer (value, self->priv->messages);
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
		break;
	}
}

void 
gmail_account_instance_init (GTypeInstance *instance, 
			     gpointer 	   g_class)
{
	GmailAccount *self = GMAIL_ACCOUNT (instance);
	self->priv = GMAIL_ACCOUNT_GET_PRIVATE (self);

	self->priv->mail = (char*) malloc (sizeof (char));
	self->priv->mail[0] = '\0';
	self->priv->password = (char*) malloc (sizeof (char));
	self->priv->password[0] = '\0';
	self->priv->messages = NULL;
	self->priv->is_disposed = 0;
}

void 
gmail_account_class_init (gpointer g_class, gpointer class_data)
{
	GObjectClass *gobject_class = G_OBJECT_CLASS (g_class);
	GmailAccountClass *klass = GMAIL_ACCOUNT_CLASS (g_class);

	gobject_class->constructor = gmail_account_constructor;
	gobject_class->dispose = gmail_account_instance_dispose;
	gobject_class->finalize = gmail_account_instance_finalize;
	klass->login = gmail_account_login;

	g_type_class_add_private (gobject_class, 
				  sizeof (GmailAccountPriv));

	GParamSpec *pspec;
	gobject_class->set_property = gmail_account_set_property;
	gobject_class->get_property = gmail_account_get_property;

	pspec = g_param_spec_string ("mail",
				     "Account mail address",
				     "Set account's mail adress",
				     "",
				     G_PARAM_READWRITE);
	g_object_class_install_property (gobject_class,
					 GMAIL_ACCOUNT_MAIL, 
					 pspec);

	pspec = g_param_spec_string ("password",
				     "Account mail password",
				     "Set/Get account's password",
				     "",
				     G_PARAM_READWRITE);
	g_object_class_install_property (gobject_class,
					 GMAIL_ACCOUNT_PASSWORD, 
					 pspec);
	pspec = g_param_spec_pointer ("message_infos", "Message Infos", 
			"Message info which holds the received messages", 
			G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
	g_object_class_install_property (gobject_class,
					 GMAIL_ACCOUNT_MESSAGES, 
					 pspec);
}

void
gmail_account_instance_dispose (GObject *object)
{
	GObjectClass *parent_class = g_type_class_peek_parent (
			GMAIL_ACCOUNT_GET_CLASS (GMAIL_ACCOUNT (object)));
	G_OBJECT_CLASS (parent_class)->dispose (object);
	gmail_parse_cleanup ();
	GMAIL_ACCOUNT (object)->priv->is_disposed = 1;
}

void 
gmail_account_instance_finalize (GObject *object) 
{
	GmailAccount *self = GMAIL_ACCOUNT (object);
	free (self->priv->mail);
	free (self->priv->password);
	GObjectClass *parent_class = g_type_class_peek_parent (
				    GMAIL_ACCOUNT_GET_CLASS (object));
	G_OBJECT_CLASS (parent_class)->finalize (object);
}

GList* 
gmail_account_login (GmailAccount *object)
{
	if (object->priv->is_disposed) {
		return NULL;
	}
	if (object->priv->messages != NULL) {
		g_list_free (object->priv->messages);
		object->priv->messages = NULL;
	}

	/* We are using here HTTP/1.0 since HTTP/1.1 uses the chunked encoding
	 * which is more difficult to handle, especially in C
	 */
	const char *http_header = "GET /mail/feed/atom HTTP/1.0\r\n"
				  "Host: mail.google.com\r\n";
	GmailAccount *self = GMAIL_ACCOUNT (object);
	g_return_val_if_fail (self != NULL, NULL);

	/* connecting to google */
	ssl_system *sys = init_ssl ();
	ssl_socket *sock = open_socket (sys, "mail.google.com", 443);
	if (sock == NULL) {
		return NULL;
	}

	size_t length_acc = strlen (self->priv->mail) + 
			    strlen (self->priv->password) + 2;
	g_return_val_if_fail (length_acc > 0, NULL);
	char *acc = (char*) malloc (length_acc);
	memset (acc, 0, length_acc);

	/* acc = self->mail + ":" + self->password */
	acc = strcat (strcat (strcat (acc, self->priv->mail), ":"), 
				   self->priv->password);

	/* an HTTP-Authorization must be base64 encoded */
	char *b64_acc = base64 ((unsigned char*) acc, strlen (acc));

	size_t length = sizeof(char) * strlen (http_header) + 
			sizeof(char) * strlen ("Authorization: Basic ") + 
			sizeof(char) * strlen (b64_acc) + 
			sizeof(char) * 5;
	g_return_val_if_fail (length > 0, NULL);
	char *msg = (char*) malloc (length);
	memset (msg, 0, length);
	/* http_header + Authorization: Basic + b64_acc + \r\n\r\n */
	msg = strcat (strcat (strcat (
	      strcat (msg, http_header), "Authorization: Basic "), b64_acc), 
	      "\r\n\r\n");

	write_sock (sys, sock, msg);

	/* freeing not needed ressources */
	free (msg);
	free (acc);
	free (b64_acc);

	size_t bytes = 0;
	char buff[1024];
	char *recv = NULL;
	size_t received = 1;

	/* actually, since the webserver closes the connection to the client we
	 * can simply receive data until the connection was closed
	 */
	memset (buff, 0, 1024);
	bytes = read_sock (sys, sock, buff, 1024);
	while (bytes > 0) {
		received += bytes;
		if (recv != NULL) {
			char *tmp = recv;
			recv = (char*) malloc (sizeof(char) * received);
			memset (recv, 0, received);
			memcpy ((void*) recv, (void*) tmp, received - bytes - 1);
			free (tmp);
		} else {
			recv = (char*) malloc (sizeof(char) * received);
			memset ((void*)recv, 0, received);
		}

		memcpy ((void*) (recv + (received - bytes - 1)), 
		        buff, bytes);
		recv[received - 1] = '\0';

		memset (buff, 0, 1024);
		bytes = read_sock (sys, sock, buff, 1024);
	}
	self->priv->messages = gmail_parse_it (recv);

	free (recv);
	close_socket (sys, sock);
	free_ssl (sys);
	return self->priv->messages;
}

GList* 
gmail_account_load ()
{
	/* loading accounts... */
	GList *accounts = NULL;
	char *path = g_strconcat (g_get_home_dir (), GMAIL_ACCOUNT_PATH, NULL);
	FILE *file = fopen (path, "rw");
	g_free (path);

	fseek (file, 0, SEEK_END);
	int size = ftell (file);
	rewind (file);
	char *buff = g_malloc (size * sizeof(char) + 1);
	fread (buff, 1, size, file);
	buff[size] = '\0';
	fclose (file);

	/* parsing file... */
	char *ln = buff;
	char *next = NULL;
	while (ln[0] != '\0') {
		next = strchr (((next == NULL) ? buff : (next + 1)), '\n');
		ln[next - ln] = '\0';

		char **split = g_strsplit (ln, " ", 3);
		accounts = g_list_append (accounts,
					  g_object_new (GMAIL_ACCOUNT_TYPE, 
					  "mail", split[0],
					  "password", split[1], NULL));
		g_strfreev (split);
		ln = next + 1;
	}

	g_free (buff);
	return accounts;
}

void
gmail_account_save_item (GmailAccount *account, FILE *fp)
{
	char *mail;
	char *pass;

	g_object_get (G_OBJECT(account), "mail", &mail, "password", &pass, NULL);
	char *buff = g_strconcat (mail, " ", pass, "\n", NULL);
	fwrite (buff, 1, strlen (buff), fp);
	g_free (buff);
}

void
gmail_account_save (GList *accounts)
{
	g_return_if_fail (accounts != NULL);

	char *path = g_strconcat (g_get_home_dir (), GMAIL_ACCOUNT_PATH, NULL);
	FILE *file = fopen (path, "w");
	g_free (path);
	g_list_foreach (accounts, (GFunc) gmail_account_save_item, file);
	fflush (file);
	fclose (file);
}

